#include "common.hlsl"

TextureCube<float> PointShadowMapTexture : register(t4);

cbuffer cbPointLightDomain : register(b0)
{
	float4x4 LightProjection : packoffset(c0);
}

cbuffer cbPointLightPixel : register(b1)
{
	float3 PointLightPos : packoffset(c0);
	float PointLightRangeRcp : packoffset(c0.w);
	float3 PointColor : packoffset(c1);
	float2 LightPerspectiveValues	: packoffset(c2);
}

float4 PointLightVS() : SV_Position
{
	return float4(0.0, 0.0, 0.0, 1.0);
}

// HS
struct HS_CONSTANT_DATA_OUTPUT
{
	float Edges[4] : SV_TessFactor;
	float Inside[2] : SV_InsideTessFactor;
};

HS_CONSTANT_DATA_OUTPUT PointLightConstantHS()
{
	HS_CONSTANT_DATA_OUTPUT Output;

	float tessFactor = 18.0f;
	Output.Edges[0] = Output.Edges[1] = Output.Edges[2] = Output.Edges[3] = tessFactor;
	Output.Inside[0] = Output.Inside[1] = tessFactor;

	return Output;
}

struct HS_OUTPUT
{
	float3 HemiDir : POSITION;
};

static const float3 HemiDir[2] = {
	float3(1.0, 1.0, 1.0),
	float3(-1.0, 1.0, -1.0)
};

[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_ccw")]
[outputcontrolpoints(4)]
[patchconstantfunc("PointLightConstantHS")]
HS_OUTPUT PointLightHS(uint PatchID : SV_PrimitiveID)
{
	HS_OUTPUT Output;
	Output.HemiDir = HemiDir[PatchID];
	return Output;
}


//DS
struct DS_OUTPUT
{
	float4 Position : SV_POSITION;
	float3 cpPos : TEXCOORD0;
};

[domain("quad")]
DS_OUTPUT PointLightDS(HS_CONSTANT_DATA_OUTPUT input, float2 UV : SV_DomainLocation, const OutputPatch<HS_OUTPUT, 4> quad)
{
	float2 posClipSpace = UV.xy * 2.0 - 1.0;
	float2 posClipSpaceAbs = abs(posClipSpace.xy);
	float maxLen = max(posClipSpaceAbs.x, posClipSpaceAbs.y);

	float3 normDir = normalize(float3(posClipSpace.xy, (maxLen - 1.0)) * quad[0].HemiDir);
	float4 posLS = float4(normDir.xyz, 1.0);

	DS_OUTPUT Output;
	Output.Position = mul(posLS, LightProjection);

	Output.cpPos = Output.Position.xyw;

	return Output;
}

float PointShadowPCF(float3 ToPixel)
{
	float3 ToPixelAbs = abs(ToPixel);
		float Z = max(ToPixelAbs.x, max(ToPixelAbs.y, ToPixelAbs.z));
	float Depth = (LightPerspectiveValues.x * Z + LightPerspectiveValues.y) / Z;
	return PointShadowMapTexture.SampleCmpLevelZero(PCFSampler, ToPixel, Depth);
}

float3 CalcPoint(float3 position, Material material, bool bUseShadow)
{
	float3 ToLight = PointLightPos - position;
	float3 ToEye = EyePosition - position;
	float DistToLight = length(ToLight);

	// Phong diffuse
	ToLight /= DistToLight; // Normalize
	float NDotL = saturate(dot(ToLight, material.normal));
	float3 finalColor = material.diffuseColor.rgb * NDotL;

	// Blinn specular
	ToEye = normalize(ToEye);
	float3 HalfWay = normalize(ToEye + ToLight);
	float NDotH = saturate(dot(HalfWay, material.normal));
	finalColor += pow(NDotH, material.specPow) * material.specIntensity;
	float shadowAtt = PointShadowPCF(position - PointLightPos);
	// Attenuation
	float DistToLightNorm = 1.0 - saturate(DistToLight * PointLightRangeRcp);
	float Attn = DistToLightNorm *DistToLightNorm;
	finalColor *= PointColor.rgb * Attn * shadowAtt;

	return finalColor;
}

float4 PointLightPS(DS_OUTPUT In) : SV_TARGET
{
	// Unpack the GBuffer
	SURFACE_DATA gbd = UnpackGBuffer_Loc(In.Position.xy);

	// Convert the data into the material structure
	Material mat;
	MaterialFromGBuffer(gbd, mat);

	// Reconstruct the world position
	float3 position = CalcWorldPos(In.cpPos.xy / In.cpPos.z, gbd.LinearDepth);

	// Calculate the light contribution
	float3 finalColor = CalcPoint(position, mat, false);

	// return the final color
	return float4(finalColor, 1.0);
}
